import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { RootState } from '../../../app/store';
import { 
  Position, 
  PositionOrder, 
  PositionInfo, 
  Order, 
  PositionStatus,
} from './domain.model';
import { 
  selectOrderById,
  selectOrdersByIds,
} from './ordersSlice';

export interface PositionsState {
  positionsList: Position[];
  status: 'idle' | 'loading' | 'failed';
};
  
const initialState: PositionsState = {
  positionsList: [],
  status: 'idle',
};

export const positionsSlice = createSlice({
  name: 'positions',
  initialState,
  reducers: {
    updatePosition: (state, action: PayloadAction<Position>) => {
      const { positionsList } = state;
      const newPosition = action.payload;
      const oldPosition = positionsList.find((position) => position.id === newPosition.id);

      if (oldPosition) {
        oldPosition.inOrders = newPosition.inOrders;
        oldPosition.outOrders = newPosition.outOrders;
        oldPosition.stopLoss = newPosition.stopLoss;
      } else {
        state.positionsList.push(newPosition);
      }
    },
  },
});

const calcPositionStatus = (inOrders: Order[], outOrders: Order[]): PositionStatus => {
  const allInOrdersOpen = inOrders.every((order) => order._status === PositionStatus.Open);
  const anyInOrdersInvested = inOrders.some((order) => order._status === PositionStatus.PartiallyFilled || order._status === PositionStatus.Closed);
  const anyOutOrdersPartiallyFilled = outOrders.some((order) => order._status === PositionStatus.PartiallyFilled);
  const allInOrdersClosed = inOrders.every((order) => order._status === PositionStatus.Closed);
  const allOutOrdersClosed = outOrders.every((order) => order._status === PositionStatus.Closed);
  const sumInOrders = inOrders.reduce((sum, order) => sum + order.amount, 0);
  const sumOutOrders = outOrders.reduce((sum, order) => sum + order.amount, 0);
  if (allInOrdersClosed && allOutOrdersClosed && sumInOrders === sumOutOrders) {
    return PositionStatus.Closed;
  } else if (allInOrdersOpen) {
    return PositionStatus.Open;
  } else if (anyOutOrdersPartiallyFilled) {
    return PositionStatus.PartiallyFilled;
  } else if (anyInOrdersInvested) {
    return PositionStatus.Invested;
  } else {
    throw new Error('calcPositionStatus: no status');
  }
}
const calcPositionOpenTime = (state: RootState, position: Position): string => {
  const firstInOrderId = position.inOrders[0];
  return firstInOrderId ? selectOrderById(state, firstInOrderId).createdAt : '-';
}
const calcPositionCloseTime = (state: RootState, position: Position): string => {
  const firstOutOrderId = position.outOrders[0];
  return firstOutOrderId ? selectOrderById(state, firstOutOrderId).createdAt : '-';
}
const calcSumOrders = (Orders: Order[]): number => {
  return  Orders.reduce((sum, order) => {
    return sum + order.amount * order.price;
  }, 0);
}
const calcBalanceDelta = (inOrders: Order[], outOrders: Order[]): number => {
  const sumInOrders = calcSumOrders(inOrders);
  const sumOutOrders = calcSumOrders(outOrders);
  return (sumOutOrders && (sumOutOrders > sumInOrders))
  ? Number.parseFloat((sumOutOrders - sumInOrders).toFixed(2))
  : 0;
}
const calcBalanceDeltaPercent = (balance: number, outOrders: Order[]): number => {
  const sumOutOrders = calcSumOrders(outOrders);
  return balance
  ? Number.parseFloat(((balance / sumOutOrders) * 100).toFixed(2))
  : 0;
}
const mapIdsToPositionOrders = (state: RootState, orderIds: string[]): PositionOrder[] => {
  return selectOrdersByIds(state, orderIds).map(({ createdAt, price, side}) => ({
    time: new Date(createdAt).getTime(),
    price,
    side,
  }))
}

export const selectPositions = (state: RootState): PositionInfo[] => (
  state.positions.positionsList.map((position: Position) => {
    const status = calcPositionStatus(selectOrdersByIds(state, position.inOrders), selectOrdersByIds(state, position.outOrders));
    const openTime = calcPositionOpenTime(state, position);
    const closeTime = calcPositionCloseTime(state, position);
    const balanceDelta = calcBalanceDelta(selectOrdersByIds(state, position.inOrders), selectOrdersByIds(state, position.outOrders));
    const balanceDeltaPercent = calcBalanceDeltaPercent(balanceDelta, selectOrdersByIds(state, position.outOrders));
    const inOrders = mapIdsToPositionOrders(state, position.inOrders);
    const outOrders = mapIdsToPositionOrders(state, position.inOrders);
    const stopLoss = position.stopLoss;
    return {
      id: position.id,
      status,
      openTime,
      closeTime,
      balanceDelta,
      balanceDeltaPercent,
      inOrders,
      outOrders,
      stopLoss, 
    }
  })
);

export const { updatePosition } = positionsSlice.actions;

export default positionsSlice.reducer;